type Callback = (data: any) => void;

enum WebSocketState {
  CONNECTING = 0,
  OPEN = 1,
  CLOSING = 2,
  CLOSED = 3
}

interface HeartbeatOptions {
  open: boolean;
  interval: number;
  msgBody: any;
}

interface ReconnectOptions {
  open: boolean;
  count: number;
  interval: number;
}

interface WebSocketOptions {
  url?: string;
  heartbeat?: HeartbeatOptions;
  reconnect?: ReconnectOptions;
  sendInterval?: number;
  onMessage?: Callback;
  onOpen?: () => void;
  onClose?: (code: number) => void;
  onError?: (event: Event) => void;
}

class MyWebSocket {
  #url: string;
  #websocket: WebSocket | null = null;
  #socket_open = false;

  #heartbeat_open = false;
  #heartbeat_timer: ReturnType<typeof setInterval> | null = null;
  #heartbeat_interval = 5000;
  #heartbeat_msgbody: any = null;

  #is_reconnect = true;
  #reconnect_count = 3;
  #reconnect_current = 0;
  #reconnect_timer: ReturnType<typeof setTimeout> | null = null;
  #reconnect_interval = 5000;

  #send_interval = 1000;
  #registerCallBack: Callback | null = null;
  #onOpen: (() => void) | null = null;
  #onClose: ((code: number) => void) | null = null;
  #onError: ((event: Event) => void) | null = null;

  constructor(url?: string) {
    this.#url = url || '';
  }

  configure(options: WebSocketOptions) {
    if (options.url) this.#url = options.url;
    if (options.heartbeat) this.setHeartBeatOption(options.heartbeat);
    if (options.reconnect) this.setReconnectOption(options.reconnect);
    if (options.sendInterval) this.setSendOption(options.sendInterval);
    if (options.onMessage) this.setRegister(options.onMessage);
    if (options.onOpen) this.#onOpen = options.onOpen;
    if (options.onClose) this.#onClose = options.onClose;
    if (options.onError) this.#onError = options.onError;
  }

  setHeartBeatOption({ open, interval, msgBody }: HeartbeatOptions) {
    this.#heartbeat_open = open;
    this.#heartbeat_interval = interval;
    this.#heartbeat_msgbody = msgBody;
  }

  setReconnectOption({ open, count, interval }: ReconnectOptions) {
    this.#is_reconnect = open;
    this.#reconnect_count = count;
    this.#reconnect_interval = interval;
  }

  setSendOption(interval: number) {
    this.#send_interval = interval;
  }

  setRegister(callBack: Callback) {
    this.#registerCallBack = callBack;
  }

  unRegister() {
    this.#registerCallBack = null;
  }

  getWebSocket() {
    return {
      socket_open: this.#socket_open,
      websocket: this.#websocket
    };
  }

  init(url?: string) {
    if (!window.WebSocket) {
      console.log('您的浏览器不支持WebSocket');
      return;
    }

    if (url) {
      this.#url = url;
    }

    if (this.#socket_open) {
      return this.#websocket;
    }

    this.#websocket = new WebSocket(this.#url);

    this.#websocket.onopen = () => {
      console.log('连接成功');
      this.#socket_open = true;
      this.#reconnect_current = 0;
      if (this.#reconnect_timer) {
        clearTimeout(this.#reconnect_timer);
        this.#reconnect_timer = null;
      }
      if (this.#heartbeat_open) {
        this.#startHeartBeat();
      }
      if (this.#onOpen) {
        this.#onOpen();
      }
    };

    this.#websocket.onmessage = (e: MessageEvent) => {
      if (this.#registerCallBack) {
        this.#registerCallBack(e.data);
      }
    };

    this.#websocket.onclose = (e: CloseEvent) => {
      console.log('连接已断开');
      this.#socket_open = false;

      if (this.#heartbeat_timer) {
        clearInterval(this.#heartbeat_timer);
        this.#heartbeat_timer = null;
      }

      if (this.#is_reconnect) {
        this.#reconnect_timer = setTimeout(() => {
          if (this.#reconnect_current >= this.#reconnect_count) {
            console.log('重连次数已达上限');
            clearTimeout(this.#reconnect_timer as any);
            this.#reconnect_timer = null;
            return;
          }
          this.#reconnect_current++;
          this.#reconnect();
        }, this.#reconnect_interval);
      }

      if (this.#onClose) {
        this.#onClose(e.code);
      }
    };

    this.#websocket.onerror = (e: Event) => {
      console.log('WebSocket连接发生错误');
      if (this.#onError) {
        this.#onError(e);
      }
    };
  }

  #startHeartBeat() {
    console.log('开启心跳');
    if (this.#heartbeat_timer) {
      clearInterval(this.#heartbeat_timer);
      this.#heartbeat_timer = null;
    }
    this.#heartbeat_timer = setInterval(() => {
      this.send(this.#heartbeat_msgbody);
    }, this.#heartbeat_interval);
  }

  #reconnect() {
    console.log(`正在发起重新连接，第${this.#reconnect_current}次`);
    if (this.#websocket && this.#socket_open) {
      this.#websocket.close();
    }
    this.init();
  }

  send(data: any, callback?: () => void) {
    if (!(this.#socket_open && this.#websocket)) {
      console.log('WebSocket未开启链接或者链接中断');
      return;
    }

    if (this.#websocket.readyState === WebSocketState.OPEN) {
      console.log('发送消息');
      this.#websocket.send(JSON.stringify(data));
      if (callback) {
        callback();
      }
    } else if (this.#websocket.readyState === WebSocketState.CONNECTING) {
      setTimeout(() => {
        this.send(data, callback);
      }, this.#send_interval);
    } else {
      if (this.#heartbeat_open) {
        this.init();
        setTimeout(() => {
          this.send(data, callback);
        }, this.#send_interval);
      } else {
        console.log('WebSocket连接已关闭');
      }
    }
  }

  close() {
    if (this.#websocket && this.#socket_open) {
      console.log('主动断开链接');
      if (this.#heartbeat_timer) {
        clearInterval(this.#heartbeat_timer);
        this.#heartbeat_timer = null;
      }
      this.#is_reconnect = false;
      this.#websocket.close();
      this.#socket_open = false;
    }
  }
}

export default MyWebSocket;

